﻿using System;
using System.ComponentModel;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Chess.Common;

namespace Chess.Server
{
    public class ServerService : IDisposable
    {
        private TcpListener _listener; // объект, принимающий TCP-клиентов
        private NetworkClientCollection _clients;
        private ChessDbModelDataContext _dbContext;

        public ServerService(int port)
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, port);
            _listener = new TcpListener(ep);
            _clients = new NetworkClientCollection();

            var chessConnectionSettings = ConfigurationManager.ConnectionStrings["ChessConnection"];
            _dbContext = new ChessDbModelDataContext(chessConnectionSettings.ConnectionString);
            if (!_dbContext.DatabaseExists())
                _dbContext.CreateDatabase();
        }

        // Connected clients
        public NetworkClientCollection Clients { get { return _clients; } }

        public event UpdateAction Update;

        private void OnUpdate(PackageType packageType, params object[] args)
        {
            if (Update != null)
                Update(packageType, args);
        }

        public void Start()
        {
            Action f = StartListeningInternal;
            Task.Run(f);
        }

        private void StartListeningInternal()
        {
            _listener.Start();
            
            while (true)
            {
               // var socket = _listener.AcceptSocket();
                TcpClient tcpClient = _listener.AcceptTcpClient();
                
                //using (NetworkStream stream = tcpClient.GetStream())
                Action f = () => ReceiveFromClient(tcpClient);
                Task.Run(f);
            }
        }

        private void ReceiveFromClient(TcpClient tcpClient)
        {
            bool clientConnected = true;
            string currentClient = null;
            while (clientConnected)
            {
                NetworkStream stream = tcpClient.GetStream();
                byte[] buffer = new byte[4096];
                try
                {
                    stream.Read(buffer, 0, buffer.Length);
                    var package = PackageSerializer.Deserialize(buffer);
                    switch (package.PackageType)
                    {
                        case PackageType.RegistrationRequest:
                            {
                                var registrationPackage = package as RegistrationRequestPackage;
                                var player = new Player();
                                player.Name = registrationPackage.UserName;
                                player.PasswordHash = registrationPackage.PasswordHash;
                                RegisterAnswerType answer;
                                if (!_dbContext.Players.Contains(player))
                                {
                                    _dbContext.Players.InsertOnSubmit(player);
                                    _dbContext.SubmitChanges();
                                    answer = RegisterAnswerType.Ok;
                                }
                                else
                                    answer = RegisterAnswerType.UserExists;
                                var registrationAnswer = package = new RegistrationAnswerPackage(answer);

                                OnUpdate(PackageType.RegistrationRequest, package);

                                SendPackageAsync(tcpClient, registrationAnswer);
                            }
                            break;
                        case PackageType.LoginRequest:
                            {
                                var loginRequestPackage = package as LoginRequestPackage;
                                var networkClient = new NetworkClient(loginRequestPackage.UserName, tcpClient);
                                networkClient.Loggined = true;

                                var player = new Player();
                                player.Name = loginRequestPackage.UserName;
                                player.PasswordHash = loginRequestPackage.PasswordHash;

                                LoginAnswerType answer;
                                var existPlayer = _dbContext.Players.FirstOrDefault(p => p.Name == player.Name);
                                if (existPlayer == null)
                                {
                                    answer = LoginAnswerType.UserNotExists;
                                }
                                else if (existPlayer.PasswordHash == player.PasswordHash)
                                {
                                    answer = LoginAnswerType.Ok;
                                    currentClient = loginRequestPackage.UserName;
                                }
                                else
                                {
                                    answer = LoginAnswerType.PasswordFailed;
                                }
                                var loginAnswer = new LoginAnswerPackage(answer);
                                if (loginAnswer.Answer == LoginAnswerType.Ok)
                                {
                                    foreach (var item in _clients.ToArray())
                                    {
                                        if (item.TcpClient.Equals(networkClient.TcpClient))
                                        {
                                            _clients.Remove(item);
                                        }
                                    }

                                    _clients.Add(networkClient);
                                }

                                SendPackageAsync(tcpClient, loginAnswer).Wait();
                                SendToAllClients(new ClientsListPackage(_clients));
                                OnUpdate(PackageType.LoginRequest, package);
                            }
                            break;
                        case PackageType.SuggestGame:
                            {
                                var suggestGamePackage = (SuggestGamePackage) package;
                                var toUser = suggestGamePackage.ToUser;
                                SendPackageAsync(_clients[toUser].TcpClient, package).Wait();
                            }
                            break;
                        case PackageType.ChessMove:
                            {
                                var movePackage = (ChessMovePackage) package;
                                var toUser = movePackage.UserTo;
                                SendPackageAsync(_clients[toUser].TcpClient, package).Wait();
                            }
                            break;
                        case PackageType.SuggestGameAnswer:
                            {
                                var suggestGameAnswer = (SuggestGameAnswerPackage) package;
                                var toUser = suggestGameAnswer.ToUser;
                                SendPackageAsync(_clients[toUser].TcpClient, package).Wait();
                            }
                            break;
                        case PackageType.LogoutRequest:
                            {
                                var logoutpackage = (LogoutRequestPackage) package;
                                _clients[logoutpackage.UserName].Loggined = false;

                                SendToAllClients(new ClientsListPackage(_clients));
                                OnUpdate(PackageType.LogoutRequest, package);
                                currentClient = null;
                            }
                            break;
                        case PackageType.Exit:
                            {
                                var exitPackage = package as ExitPackage;
                                var networkClient = new NetworkClient(exitPackage.UserName, tcpClient);
                                _clients.Remove(networkClient.ClientName);
                                OnUpdate(PackageType.LogoutRequest, package);
                                clientConnected = false;
                                currentClient = null;
                                break;
                            }
                        case PackageType.StartGame:
                            {
                                var startGamePackage = package as StartGamePackage;
                            }
                            break;
                        case PackageType.EndGame:
                            {
                                var endGamePackage = package as EndGamePackage;
                            }
                            break;
                        case PackageType.Message:
                            {
                                var messagePackage = package as MessagePackage;
                            }
                            break;
                        case PackageType.ChangePosition:
                            {
                                var changePositionPackage = package as ChangePositionPackage;
                            }
                            break;
                    }
                }
                catch (IOException e)
                {
                    tcpClient.Close();
                    if (currentClient != null)
                    {
                        _clients.Remove(currentClient);
                        OnUpdate(PackageType.Exit, null);
                        SendToAllClients(new ClientsListPackage(_clients));
                        
                        currentClient = null;
                    }
                    clientConnected = false;
                    //tcpClient.Close();
                }
                finally
                {
                    // tcpClient.Close();
                }
            }
        }

        private Task SendPackageAsync(TcpClient tcpClient, Package package)
        {
            var eventArgs = new SocketAsyncEventArgs();
            var bytes = PackageSerializer.Serialize(package);
            var header = BitConverter.GetBytes(bytes.Length);
            var bytesToWrite = header.Concat(bytes).ToArray();
            eventArgs.SetBuffer(bytesToWrite, 0, bytesToWrite.Length);

            return tcpClient.GetStream().WriteAsync(bytesToWrite, 0, bytesToWrite.Length);
        }

        private void SendToAllClients(Package package)
        {
            foreach (var client in Clients)
            {
                SendPackageAsync(client.TcpClient, package).Wait();
            }
        }

        public void StopListening()
        {
            _listener.Stop();
        }

        public void Dispose()
        {
            StopListening();
        }

        private int _nextId;
        public int GetNextId()
        {
            return _nextId++;
        }
    }
}
